﻿/*
 * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
 *
 * This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit).
 *
 * Use of this source code is governed by MIT license that can be found in the
 * LICENSE file in the root of the source tree. All contributing project authors
 * may be found in the AUTHORS file in the root of the source tree.
 */

#ifdef ENABLE_MP4

#include "MP4Reader.h"
#include "Common/config.h"
#include "Sqlite/DbRecord.h"
#include "Thread/WorkThreadPool.h"
#include "Util/FileTime.h"

using namespace std;
using namespace toolkit;

namespace mediakit {

MP4Reader::MP4Reader(const string &vhost, const string &app, const string &stream_id, const string &file_path) {
    _file_path = file_path;
    if (_file_path.empty()) {
        GET_CONFIG(string, recordPath, Record::kFilePath);
        GET_CONFIG(bool, enableVhost, General::kEnableVhost);
        if (enableVhost) {
            _file_path = vhost + "/" + app + "/" + stream_id;
        } else {
            _file_path = app + "/" + stream_id;
        }
        _file_path = File::absolutePath(_file_path, recordPath);
    }

    _demuxer = std::make_shared<MP4Demuxer>();
    _demuxer->openMP4(_file_path);

    if (stream_id.empty()) {
        return;
    }
    ProtocolOption option;
    //读取mp4文件并流化时，不重复生成mp4/hls文件
    option.enable_mp4 = false;
    option.enable_hls = false;
    _muxer
        = std::make_shared<MultiMediaSourceMuxer>(vhost, app, stream_id, _demuxer->getDurationMS() / 1000.0f, option);
    auto tracks = _demuxer->getTracks(false);
    if (tracks.empty()) {
        throw std::runtime_error(StrPrinter << "该mp4文件没有有效的track:" << _file_path);
    }
    for (auto &track : tracks) {
        _muxer->addTrack(track);
        if (track->getTrackType() == TrackVideo) {
            _have_video = true;
        }
    }
    //添加完毕所有track，防止单track情况下最大等待3秒
    _muxer->addTrackCompleted();
}

// TODO: 读取保存的文件直到结束
bool MP4Reader::readSample() {
    if (_paused) {
        //确保暂停时，时间轴不走动
        _seek_ticker.resetTime();
        return true;
    }

    bool keyFrame = false;
    bool eof = false;
    while (!eof && _last_dts < getCurrentStamp()) {
        auto frame = _demuxer->readFrame(keyFrame, eof);
        if (!frame) {
            continue;
        }
        _last_dts = frame->dts();

        //InfoL << "pts: " << frame->pts() << "   dts: " << frame->dts();

        if (_muxer) {
            _muxer->inputFrame(frame);
        }
    }

    GET_CONFIG(bool, file_repeat, Record::kFileRepeat);
    if (eof && (file_repeat || _file_repeat)) {
        //需要从头开始看
        seekTo(0);
        return true;
    }

    return !eof;
}

bool MP4Reader::readNextSample() {
    bool keyFrame = false;
    bool eof = false;
    auto frame = _demuxer->readFrame(keyFrame, eof);
    if (!frame) {
        return false;
    }
    if (_muxer) {
        _muxer->inputFrame(frame);
    }
    setCurrentStamp(frame->dts());
    return true;
}

void MP4Reader::stopReadMP4() {
    _timer = nullptr;
}

void MP4Reader::startReadMP4(const EventPoller::Ptr &poller_in, uint64_t sample_ms, bool ref_self, bool file_repeat) {
    GET_CONFIG(uint32_t, sampleMS, Record::kSampleMS);
    auto strong_self = shared_from_this();
    if (_muxer) {
        _muxer->setMediaListener(strong_self);
        //一直读到所有track就绪为止
        while (!_muxer->isAllTrackReady() && readNextSample()) {
        }
    }

    //未指定线程，那么使用后台线程(读写文件采用后台线程)
    auto poller = poller_in ? poller_in : WorkThreadPool::Instance().getPoller();
    auto timer_sec = (sample_ms ? sample_ms : sampleMS) / 1000.0f;

    //启动定时器
    if (ref_self) {
        _timer = std::make_shared<Timer>(
            timer_sec,
            [strong_self]() {
                lock_guard<recursive_mutex> lck(strong_self->_mtx);
                return strong_self->readSample();
            },
            poller);
    } else {
        weak_ptr<MP4Reader> weak_self = strong_self;
        _timer = std::make_shared<Timer>(
            timer_sec,
            [weak_self]() {
                auto strong_self = weak_self.lock();
                if (!strong_self) {
                    return false;
                }
                lock_guard<recursive_mutex> lck(strong_self->_mtx);
                return strong_self->readSample();
            },
            poller);
    }

    _file_repeat = file_repeat;
}

const MP4Demuxer::Ptr &MP4Reader::getDemuxer() const {
    return _demuxer;
}

uint32_t MP4Reader::getCurrentStamp() {
    return (uint32_t)(_seek_to + !_paused * _speed * _seek_ticker.elapsedTime());
}

void MP4Reader::setCurrentStamp(uint32_t new_stamp) {
    auto old_stamp = getCurrentStamp();
    _seek_to = new_stamp;
    _last_dts = new_stamp;
    _seek_ticker.resetTime();
    if (old_stamp != new_stamp && _muxer) {
        //时间轴未拖动时不操作
        _muxer->setTimeStamp(new_stamp);
    }
}

bool MP4Reader::seekTo(MediaSource &sender, uint32_t stamp) {
    //拖动进度条后应该恢复播放
    pause(sender, false);
    TraceL << getOriginUrl(sender) << ",stamp:" << stamp;
    return seekTo(stamp);
}

bool MP4Reader::pause(MediaSource &sender, bool pause) {
    if (_paused == pause) {
        return true;
    }
    //_seek_ticker重新计时，不管是暂停还是seek都不影响总的播放进度
    setCurrentStamp(getCurrentStamp());
    _paused = pause;
    TraceL << getOriginUrl(sender) << ",pause:" << pause;
    return true;
}

bool MP4Reader::speed(MediaSource &sender, float speed) {
    if (speed < 0.1 && speed > 20) {
        WarnL << "播放速度取值范围非法:" << speed;
        return false;
    }
    //设置播放速度后应该恢复播放
    pause(sender, false);
    if (_speed == speed) {
        return true;
    }
    _speed = speed;
    TraceL << getOriginUrl(sender) << ",speed:" << speed;
    return true;
}

bool MP4Reader::seekTo(uint32_t stamp_seek) {
    lock_guard<recursive_mutex> lck(_mtx);
    if (stamp_seek > _demuxer->getDurationMS()) {
        //超过文件长度
        return false;
    }
    auto stamp = _demuxer->seekTo(stamp_seek);
    if (stamp == -1) {
        // seek失败
        return false;
    }

    if (!_have_video) {
        //没有视频，不需要搜索关键帧；设置当前时间戳
        setCurrentStamp((uint32_t)stamp);
        return true;
    }
    //搜索到下一帧关键帧
    bool keyFrame = false;
    bool eof = false;
    while (!eof) {
        auto frame = _demuxer->readFrame(keyFrame, eof);
        if (!frame) {
            //文件读完了都未找到下一帧关键帧
            continue;
        }
        if (keyFrame || frame->keyFrame() || frame->configFrame()) {
            //定位到key帧
            if (_muxer) {
                _muxer->inputFrame(frame);
            }
            //设置当前时间戳
            setCurrentStamp(frame->dts());
            return true;
        }
    }
    return false;
}

bool MP4Reader::close(MediaSource &sender, bool force) {
    if (!_muxer || (!force && _muxer->totalReaderCount())) {
        return false;
    }
    _timer.reset();
    WarnL << sender.getSchema() << "/" << sender.getVhost() << "/" << sender.getApp() << "/" << sender.getId() << " "
          << force;
    return true;
}

int MP4Reader::totalReaderCount(MediaSource &sender) {
    return _muxer ? _muxer->totalReaderCount() : sender.readerCount();
}

MediaOriginType MP4Reader::getOriginType(MediaSource &sender) const {
    return MediaOriginType::mp4_vod;
}

string MP4Reader::getOriginUrl(MediaSource &sender) const {
    return _file_path;
}

//------------------------------------------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------------------------------------------

/// @brief 传入开始时间、结束时间参数，根据时间查找对应的录像文件以及每个文件播放的起始时间，逐一读取并打包发送
/// @param vhost
/// @param app
/// @param stream_id        地址
/// @param start_time       RTSP传入的点播开始时间
/// @param end_time         RTSP传入的点播结束时间
MultiMP4Reader::MultiMP4Reader(
    const string &host, const string &vhost, const string &app, const string &stream_id, const uint64_t &start_time,
    const uint64_t &end_time) {

    // 是否启用资源映射
    std::shared_ptr<MediaInfo> mapping_media = nullptr;
    GET_CONFIG(bool, enableHistoryMapping, ResourceMapping::kHistoryEnable);
    if (enableHistoryMapping) {
        GET_CONFIG(string, history_media_resource, ResourceMapping::kHistoryResource);
        auto media = Parser::parseShortURL(history_media_resource);
        //资源路径相同
        if (media && strcmp(app.c_str(), media->_app.c_str()) == 0
            && strcmp(stream_id.c_str(), media->_streamid.c_str()) == 0) {
            GET_CONFIG(string, history_media_mapping, ResourceMapping::kHistoryMapping);
            //解析映射内容
            auto vec_mappings = Parser::parseResourceMapping(history_media_mapping);
            auto iter = std::find_if(
                vec_mappings.begin(), vec_mappings.end(),
                [host](std::tuple<std::string, std::shared_ptr<MediaInfo>> &item) {
                    std::string text = host;
                    std::string ip = text;
                    auto pos = text.find_first_of('@');
                    if (pos != std::string::npos) {
                        ip = ip.substr(pos + 1);
                    }
                    InfoL << "判断映射信息: " << text << "  ->  " << ip;
                    return strcmp(std::get<0>(item).c_str(), ip.c_str()) == 0;
                });
            //最后要映射的内容
            if (iter != vec_mappings.end()) {
                mapping_media = std::get<1>(*iter);
            }
        }
    }

    //如果需要映射资源，则根据映射后的资源id进行数据库查找
    auto stream = stream_id;
    if (mapping_media) {
        stream = mapping_media->_streamid;
    }

    auto files = DbRecord::Instance().Query(stream, start_time, end_time);
    if (files.empty()) {
        return;
    }

    _exists = true; //数据库中存在对应的文件记录

    auto poller = WorkThreadPool::Instance().getPoller();
    // poller->async([this, start_time, end_time, files, vhost, app, stream_id]() {
    auto t1 = std::chrono::high_resolution_clock::now();

    InfoL << "------ Query :(" << CFileTime(start_time).Format() << "," << CFileTime(end_time).Format() << ")";

    //遍历文件，并计算开始时间和结束时间
    GET_CONFIG(string, recordPath, Record::kFilePath);
    for (auto i = 0; i < files.size(); i++) {
        auto info = files[i];
        FileSegInfo seg;
        seg.file_path = File::absolutePath(info->file, recordPath);

        //文件的开始时间
        if (i == 0) {
            auto dis = CFileTime(start_time) - CFileTime(info->start);
            seg.begin_time = dis.GetTotalMillisecond() / 40 * 40; //凑个整，和dts时间戳保持同样的单位大小
        } else {
            seg.begin_time = 0;
        }
        //文件的结束时间
        if (i == files.size() - 1) {
            auto dis = CFileTime(end_time) - CFileTime(info->start);
            seg.end_time = dis.GetTotalMillisecond() / 40 * 40; //凑个整，和pts时间戳保持同样的单位大小
        } else {
            seg.end_time = -1;
        }
        _file_segs.push_back(seg);

        InfoL << "File-> idx:" << i << " \t(" << CFileTime(info->start).Format() << "," << CFileTime(info->end).Format()
              << ") \tSeg:(" << seg.begin_time << "," << seg.end_time << ")";
    }

    //回放的总时间，会通过rtsp返回给客户端
    _duration_ms = (CFileTime(end_time) - CFileTime(start_time)).GetTotalMillisecond();
    _current_file_index = 0;
    _file_path = _file_segs[_current_file_index].file_path;

    _demuxer = std::make_shared<MP4Demuxer>();
    _demuxer->openMP4(_file_path);

    if (stream_id.empty()) {
        return;
    }
    ProtocolOption option;
    //读取mp4文件并流化时，不重复生成mp4/hls文件
    option.enable_mp4 = false;
    option.enable_hls = false;

    _muxer = std::make_shared<MultiMediaSourceMuxer>(vhost, app, stream_id, _duration_ms / 1000.0f, option);
    auto tracks = _demuxer->getTracks(false);
    if (tracks.empty()) {
        throw std::runtime_error(StrPrinter << "该mp4文件没有有效的track:" << _file_path);
    }
    for (auto &track : tracks) {
        _muxer->addTrack(track);
        if (track->getTrackType() == TrackVideo) {
            _have_video = true;
        }
    }

    auto ret = skipTo(_file_segs[0].begin_time);
    if (!ret) { //在第一个文件未找到关键帧，可能时间正好在文件的末尾
        if (_file_segs.size() > 1) { //第二个文件就从头开始播放
            _current_file_index = 1;
            _file_path = _file_segs[_current_file_index].file_path;
            _demuxer = std::make_shared<MP4Demuxer>();
            _demuxer->openMP4(_file_path);
        } else {
            _exists = false;
        }
    }

    //添加完毕所有track，防止单track情况下最大等待3秒
    _muxer->addTrackCompleted();

    auto t2 = std::chrono::high_resolution_clock::now();
    WarnL << "createFromMultiMP4 ：Time - " << std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count();
    //});
}

MultiMP4Reader::~MultiMP4Reader() {
    stopReadMP4();
}

// TODO: 读取保存的文件直到结束
bool MultiMP4Reader::readSample() {
    if (_paused) {
        //确保暂停时，时间轴不走动
        _seek_ticker.resetTime();
        return true;
    }

    bool finish = false;
    bool keyFrame = false;
    // return !eof;
    //  -------------------------------------------------------------------------------------------

    auto current_stamp = getCurrentStamp();
    while (_last_dts < current_stamp) {
        // WarnL << "---------->> DTS: " << _last_dts << "       STAMP: " << current_stamp;
        bool eof = false;
        auto frame = _demuxer->readFrame(keyFrame, eof);
        if (!frame) {
            _last_file_dts = _last_dts;
            _current_file_index++;
            if (_current_file_index >= _file_segs.size()) {
                finish = true;
                return false;
            } else { //继续打开下一个文件
                _file_path = _file_segs[_current_file_index].file_path;
                WarnL << "File:  " << _file_path << "   DTS: " << _last_file_dts;
                _demuxer = std::make_shared<MP4Demuxer>();
                _demuxer->openMP4(_file_path);
                return true;
            }
        }

        //计算pts、dts
        auto imp = dynamic_pointer_cast<FrameFromPtr>(frame);
        // InfoL << "pts: " << frame->pts() << "   dts: " << frame->dts();
        if (_current_file_index == 0) {
            imp->setDts(imp->dts() - _file_segs[0].begin_time);
            imp->setPts(imp->pts() - _file_segs[0].begin_time);
        } else {
            imp->setDts(imp->dts() + _last_file_dts);
            imp->setPts(imp->pts() + _last_file_dts);
        }

        // InfoL << "PTS: " << frame->pts() << "   DTS: " << frame->dts();
        _last_dts = frame->dts();

        if (_muxer) {
            _muxer->inputFrame(frame);
        }

        //到了结束时间，这个结束时间是按照时长计算的，可能并不太准确和实际传入的结束时间相符，但是差别也不是很大，懒得修改
        if (_last_dts >= _duration_ms) {
            return false;
        }
    }
    // -------------------------------------------------------------------------------------------

    // GET_CONFIG(bool, file_repeat, Record::kFileRepeat);
    // if (eof && (file_repeat || _file_repeat)) {
    //     //需要从头开始看
    //     seekTo(0);
    //     return true;
    // }
    // InfoL << "---------------------------end-----------------------";

    return !finish;
}

bool MultiMP4Reader::readNextSample() {
    bool keyFrame = false;
    bool eof = false;
    auto frame = _demuxer->readFrame(keyFrame, eof);
    if (!frame) {
        return false;
    }

    if (_muxer) {
        _muxer->inputFrame(frame);
        auto imp = dynamic_pointer_cast<FrameFromPtr>(frame);
        if (_current_file_index == 0) {
            if (imp->dts() < _file_segs[0].begin_time) {
                imp->setDts(0);
            } else {
                imp->setDts(imp->dts() - _file_segs[0].begin_time);
            }

            if (imp->pts() < _file_segs[0].begin_time) {
                imp->setPts(0);
            } else {
                imp->setPts(imp->pts() - _file_segs[0].begin_time);
            }
        } else {
            imp->setDts(imp->dts() + _last_file_dts);
            imp->setPts(imp->pts() + _last_file_dts);
        }

        _last_dts = imp->dts();
    }
    setCurrentStamp(frame->dts());
    return true;
}

void MultiMP4Reader::stopReadMP4() {
    _timer = nullptr;
    // InfoL << "------------------stopReadMP4...";
}

void MultiMP4Reader::startReadMP4(
    const EventPoller::Ptr &poller_in, uint64_t sample_ms, bool ref_self, bool file_repeat) {
    GET_CONFIG(uint32_t, sampleMS, Record::kSampleMS);
    auto strong_self = shared_from_this();
    if (_muxer) {
        _muxer->setMediaListener(strong_self);
        //一直读到所有track就绪为止
        while (!_muxer->isAllTrackReady() && readNextSample()) {
        }
    }

    //未指定线程，那么使用后台线程(读写文件采用后台线程)
    auto poller = poller_in ? poller_in : WorkThreadPool::Instance().getPoller();
    auto timer_sec = (sample_ms ? sample_ms : sampleMS) / 1000.0f;

    //启动定时器
    if (ref_self) {
        _timer = std::make_shared<Timer>(
            timer_sec,
            [strong_self]() {
                lock_guard<recursive_mutex> lck(strong_self->_mtx);
                return strong_self->readSample();
            },
            poller);
    } else {
        weak_ptr<MultiMP4Reader> weak_self = strong_self;
        _timer = std::make_shared<Timer>(
            timer_sec,
            [weak_self]() {
                auto strong_self = weak_self.lock();
                if (!strong_self) {
                    return false;
                }
                lock_guard<recursive_mutex> lck(strong_self->_mtx);
                return strong_self->readSample();
            },
            poller);
    }

    _file_repeat = file_repeat;
}

const MP4Demuxer::Ptr &MultiMP4Reader::getDemuxer() const {
    return _demuxer;
}

bool MultiMP4Reader::Exists() {
    return _exists;
}

uint32_t MultiMP4Reader::getCurrentStamp() {
    return (uint32_t)(_seek_to + !_paused * _speed * _seek_ticker.elapsedTime());
}

void MultiMP4Reader::setCurrentStamp(uint32_t new_stamp) {
    auto old_stamp = getCurrentStamp();
    _seek_to = new_stamp;
    _last_dts = new_stamp;
    _seek_ticker.resetTime();
    if (old_stamp != new_stamp && _muxer) {
        //时间轴未拖动时不操作
        _muxer->setTimeStamp(new_stamp);
    }
}

/**
 * TODO: 这里根据时间计算出对应播放的文件以及对应时刻，然后需要跳转到对应文件的指定时刻
 * 需要记录每个文件前一个文件结束时dts
 */
bool MultiMP4Reader::seekTo(MediaSource &sender, uint32_t stamp) {
    //拖动进度条后应该恢复播放
    pause(sender, false);
    DebugL << getOriginUrl(sender) << ",stamp:" << stamp;
    return seekTo(stamp);
}

bool MultiMP4Reader::pause(MediaSource &sender, bool pause) {
    if (_paused == pause) {
        return true;
    }
    //_seek_ticker重新计时，不管是暂停还是seek都不影响总的播放进度
    setCurrentStamp(getCurrentStamp());
    _paused = pause;
    TraceL << getOriginUrl(sender) << ",pause:" << pause;
    return true;
}

bool MultiMP4Reader::speed(MediaSource &sender, float speed) {
    if (speed < 0.1 && speed > 20) {
        WarnL << "播放速度取值范围非法:" << speed;
        return false;
    }
    //设置播放速度后应该恢复播放
    pause(sender, false);
    if (_speed == speed) {
        return true;
    }
    _speed = speed;
    TraceL << getOriginUrl(sender) << ",speed:" << speed;
    return true;
}

/**
 * TODO: 这里根据时间计算出对应播放的文件以及对应时刻，然后需要跳转到对应文件的指定时刻
 * 需要记录每个文件前一个文件结束时dts
 */
bool MultiMP4Reader::seekTo(uint32_t stamp_seek) {
    // lock_guard<recursive_mutex> lck(_mtx);
    // if (stamp_seek > _demuxer->getDurationMS()) {
    //     //超过文件长度
    //     return false;
    // }
    // auto stamp = _demuxer->seekTo(stamp_seek);
    // if (stamp == -1) {
    //     // seek失败
    //     return false;
    // }

    // if (!_have_video) {
    //     //没有视频，不需要搜索关键帧；设置当前时间戳
    //     setCurrentStamp((uint32_t)stamp);
    //     return true;
    // }
    ////搜索到下一帧关键帧
    // bool keyFrame = false;
    // bool eof = false;
    // while (!eof) {
    //     auto frame = _demuxer->readFrame(keyFrame, eof);
    //     if (!frame) {
    //         //文件读完了都未找到下一帧关键帧
    //         continue;
    //     }
    //     if (keyFrame || frame->keyFrame() || frame->configFrame()) {

    //        // TODO
    //        auto imp = dynamic_pointer_cast<FrameFromPtr>(frame);
    //        imp->setDts(imp->dts() - _file_segs[0].begin_time * 1000);
    //        imp->setPts(imp->pts() - _file_segs[0].begin_time * 1000);
    //        _last_dts = imp->dts();

    //        //定位到key帧
    //        if (_muxer) {
    //            _muxer->inputFrame(frame);
    //        }
    //        //设置当前时间戳
    //        setCurrentStamp(frame->dts());
    //        return true;
    //    }
    //}

    // if (stamp_seek == 0) {
    //     return false;
    // }
    DebugL << "------------seekto: " << stamp_seek;

    return false;
}

bool MultiMP4Reader::skipTo(uint32_t stamp_seek) {
    lock_guard<recursive_mutex> lck(_mtx);
    // if (_current_file_index != 0) {
    //     return true;
    // }

    if (stamp_seek > _demuxer->getDurationMS()) {
        //超过文件长度
        return false;
    }
    auto stamp = _demuxer->seekTo(stamp_seek);
    if (stamp == -1) {
        // seek失败
        return false;
    }

    if (!_have_video) {
        //没有视频，不需要搜索关键帧；设置当前时间戳
        setCurrentStamp((uint32_t)stamp);
        return true;
    }
    //搜索到下一帧关键帧
    bool keyFrame = false;
    bool eof = false;
    while (!eof) {
        auto frame = _demuxer->readFrame(keyFrame, eof);
        if (!frame) {
            //文件读完了都未找到下一帧关键帧
            continue;
        }
        if (keyFrame || frame->keyFrame() || frame->configFrame()) {

            // TODO 判断是否跳转到对应的位置，并计算pts、dts
            auto imp = dynamic_pointer_cast<FrameFromPtr>(frame);
            if (_current_file_index == 0) {
                //当小于开始时间，查找下一帧数据，保证dts值大于开始时间，方便下面计算实际的dts
                if (imp->dts() < _file_segs[0].begin_time) {
                    continue;
                } else {
                    imp->setDts(imp->dts() - _file_segs[0].begin_time);
                    imp->setPts(imp->pts() - _file_segs[0].begin_time);
                }
            } else { //这部分基本情况下不会调用，只会在第一个文件时调用skipTo
                imp->setDts(imp->dts() + _last_file_dts);
                imp->setPts(imp->pts() + _last_file_dts);
            }
            _last_dts = imp->dts();

            //定位到key帧
            if (_muxer) {
                _muxer->inputFrame(frame);
            }
            //设置当前时间戳
            setCurrentStamp(frame->dts());
            return true;
        }
    }
    return false;
}

bool MultiMP4Reader::close(MediaSource &sender, bool force) {
    if (!_muxer || (!force && _muxer->totalReaderCount())) {
        return false;
    }
    _timer.reset();
    WarnL << sender.getSchema() << "/" << sender.getVhost() << "/" << sender.getApp() << "/" << sender.getId() << " "
          << force;
    return true;
}

int MultiMP4Reader::totalReaderCount(MediaSource &sender) {
    return _muxer ? _muxer->totalReaderCount() : sender.readerCount();
}

MediaOriginType MultiMP4Reader::getOriginType(MediaSource &sender) const {
    return MediaOriginType::mp4_vod;
}

string MultiMP4Reader::getOriginUrl(MediaSource &sender) const {
    return _file_path;
}

} /* namespace mediakit */
#endif // ENABLE_MP4